/**
 * Simple Restful Client
 */

var http = require("http");
var url = require("url");
var form_request = require("request");
var _ = require("lodash");
var Promise = require("bluebird");
var io = require('socket.io-client');

function objToQueryParam(obj) {
    var queryParams = [];

    for (var key in obj) {
        if (!obj.hasOwnProperty(key)) {
            continue;
        }
        var paramValue = obj[key];
        if (Array.isArray(obj[key])) {
            paramValue = obj[key].map(function(item) {
                encodeURIComponent(item);
            }).join(",");
        } else {
            paramValue = encodeURIComponent(paramValue);
        }

        queryParams.push(key + "=" + paramValue);
    }

    return queryParams.join("&");
}


function urlJoin() {
    var args = Array.prototype.slice.call(arguments);
    var pathParts = [];

    for (var i = 0, l = args.length; i < l; i++) {
        var arg = args[i];
        if (arg === "") {
            continue;
        }
        if (arg[0] === "/") {
            arg = arg.substring(1, arg.length);
        }
        if (arg[arg.length - 1] === "/") {
            arg = arg.substring(0, arg.length - 1);
        }
        if (arg === "") {
            continue;
        }

        pathParts.push(arg);
    }

    return "/" + pathParts.join("/");
}


function doRequest(method, path, options, reqData, header) {
    return new Promise(function(resolve, reject) {
        var reqOptions = _.clone(options);
        var isSendData = false;
        var headers = {
            'Content-Type': 'application/json; charset=utf-8'
        };

        // add header
        if(header) {
            for(k in header) {
                headers[k] = header[k]
            }
        }
        // is buffer
        if (typeof(reqData) == "object")
            headers['Content-Type'] = 'application/octet-stream';

        reqOptions.path = urlJoin(reqOptions.path, path);
        reqOptions.method = method;

        if (method === "POST" || method === "PUT" || method === "PATCH") {
            if(typeof(reqData) == "object") {
                headers["Content-Length"] = Buffer.byteLength(reqData,'binary');
            } else {
                headers["Content-Length"] = Buffer.byteLength(reqData,'utf8');
            }
            isSendData = true;
        }

        reqOptions.headers = headers;

        var request = http.request(reqOptions, function(res) {
            var buffList = [];
            var buff = "";
            res.on("data", function(data) {
                buffList.push(data);
            });

            res.on("end", function(data) {
                if (data) {
                    buffList.push(data);
                }

                buff = Buffer.concat(buffList);
                try {
                    var d;
                    /* May not json */
                    try {
                        d = JSON.parse(buff.toString("utf-8"));
                    } catch (e) {
                        d = buff.toString("utf-8");
                    }
                    resolve(d);
                } catch(e) {
                    reject(e);
                }
            });
        });

        request.on("error", function(err) {
            //console.log("Error: " + err);
            reject(err);
        });
		
		request.on('socket', function (socket) {
			socket.setTimeout(9000);
			socket.on('timeout', function () {
				request.abort();
			})
		});

        if (isSendData) {
            request.write(reqData);
        }

        request.end();
    });

}


function RestfulClient(serverUrl) {
    this.defaultReqHeader = {};

    this.init(serverUrl);
}

RestfulClient.prototype.init = function(serverUrl) {
    var urlSchema = url.parse(serverUrl);

    this.defaultReqHeader.hostname = urlSchema.hostname;
    var port =  urlSchema.port || "80";
    this.defaultReqHeader.port = parseInt(port);
    this.defaultReqHeader.path = urlSchema.path || "";
};

RestfulClient.prototype.get = function(path, params) {
    var query = objToQueryParam(params);
    var fullPath = path + (query !== "" ? "?" + query: "");
    return doRequest("GET", fullPath, this.defaultReqHeader);
};

RestfulClient.prototype.post = function(path, params, data) {
    var query = objToQueryParam(params);
    var fullPath = path + (query !== "" ? "?" + query: "");
    return doRequest("POST", fullPath, this.defaultReqHeader, JSON.stringify(data));
};

RestfulClient.prototype.post_binary = function(path, params, data) {
    var query = objToQueryParam(params);
    var fullPath = path + (query !== "" ? "?" + query: "");
    return doRequest("POST", fullPath, this.defaultReqHeader, data);
};

RestfulClient.prototype.put = function(path, params, data) {
    var query = objToQueryParam(params);
    var fullPath = path + (query !== "" ? "?" + query: "");
    return doRequest("PUT", fullPath, this.defaultReqHeader, JSON.stringify(data));
};

RestfulClient.prototype.delete = function(path, params, header) {
    var query = objToQueryParam(params);
    var fullPath = path + (query !== "" ? "?" + query: "");
    return doRequest("DELETE", fullPath, this.defaultReqHeader, null, header);
};

RestfulClient.prototype.post_form = function(url, form) {
    return new Promise(function(reslove, reject) {
        form_request.post({url: url, formData: form}, function(err, httpResponse, body) {
            if (err) {
                reject(err);
            } else {
                reslove(body);
            }
        });
    })
};

var APP = function(url) {
    this.client = new RestfulClient(url);
    this.frames = []
    this.datas = new Buffer([]);
    this.auth_path = '/auth'
    this.add_monitor_path = '/api/monitors'
    this.del_monitor_path = '/api/monitors/'
    this.push_message_path = '/api/device/action/send'
}

APP.prototype.auth = function(user, pwd) {
    var self = this;
    return this.client.post(self.auth_path, "", {username: user, passwd: pwd})
                    .then(function(ret) {
                            if(ret.success) {
                                self.token = ret.token
                                return ret.token;
                            } else {
                                throw new Error("Auth Failed!!!")
                            }
                        })
}

APP.prototype.add_monitor = function(url, method, device_id) {
    var self = this;
    return this.client.post(self.add_monitor_path, "", {token: self.token, device_id: device_id})
                .then(function(ret) {
                    if(ret.success) {
                        self.monitor_uri = ret.uri
                        return true
                    } else {
                        throw new Error("Add Monitor Failed!!!")
                    }
                })
}


// parse protocol
const SOF = 0xFFFF
const EOF = 0xFFEE
const MIN_LEN = 2 + 2 + 2

function parse_protocol_buffer(data) {
    var frame = []
    console.log("data: ", data)
    while(true) {
        if(data.length < MIN_LEN) {
            break; //nothing
        } else {
            var start = data[0]*256 + data[1];
            var protocol_len = data[5] * 256 + data[4];
            var type = data[3] * 256 + data[2];
            if (start != SOF) {
                data = data.slice(1);
                continue;
            } else if (protocol_len <= (data.length - 2 - 6)) {

                frame.push(data.slice(6, protocol_len + 6))
                data = data.slice(protocol_len + MIN_LEN + 2 - 1)
            } else {
                // wait buffer
                break; // protocol_len > data.length
            }
        }
    }
    console.log("parse frame: ", frame, data)
    return {frame: frame, data: data}
}

APP.prototype.monitor_auth = function() {
    this.socket = new io.connect("ws://" + this.monitor_uri.ws)
    var self = this;
    return new Promise(function(resolve, reject) {
        self.socket.on('data', function(data) {
            //console.log("recved frame ", frame)
            self.datas = Buffer.concat([self.datas, data])
            obj = parse_protocol_buffer(self.datas)
            self.datas = obj.data
            obj.frame.forEach(function(frame, i) { 
                self.frames.push(frame)
            })
        })

        self.socket.on('connect', function() {
            self.socket.emit("auth", self.monitor_uri.token)
            resolve(true)
        })
    })
}

APP.prototype.get_frame = function(timeout, count) {
    var self = this;

    return new Promise(function(resolve, reject) {
        if(self.frames.length == count) {
            resolve(self.frames)
        } else {
            setTimeout(function(){
                if(self.frames.length == count) {
                    resolve(self.frames)
                } else {
                    reject(new Error("Wait timeout and no frame recevied!!!"))
                }
            }, timeout)
        }
        
    })
}

function build_rfid_packet(rfids) {
    var packet = new Buffer(rfids);
    var hd = new Buffer([0xff, 0xff, 0x01, 0x00, packet.length % 256, packet.length / 256]);
    var end = new Buffer([0xff, 0xee]);
    packet = Buffer.concat([hd, packet, end])
    console.log("Build Rfid packet :", packet)
    return packet.toString('base64');
}

APP.prototype.push_message = function(device_id, message, count) {
    var self = this;
    return new Promise(function(resolve, reject) {
        while(count --) {

            (function(i) {
                self.client.post(self.push_message_path, "", {token: self.token, device_id: device_id, message: build_rfid_packet(message), translate: "base64tobuf"})
                .then(function(ret) {
                    if(ret.success) {
                        if(i == 0) {
                            resolve(true)
                        }
                    } else {
                        reject(new Error("Push Message Failed!!!"))
                    }
                })
            })(count);
        }
    })
}

APP.prototype.del_monitor = function(device_id) {
    return this.client.delete(this.del_monitor_path + device_id, "", {"yunt-token": this.token})
                    .then(function(ret) {
                            if(ret.success) {
                                return true
                            } else {
                                throw new Error("Del monitor Failed!!!")
                            }
                        })
}

APP.prototype.end = function(id) {
    this.del_monitor(id)
    if(this.socket) {
        this.socket.disconnect()
    }
}

// Main Entry

function main() {
    var api_url = 'http://iot-i.cn:6690';
    var device_id = 'm_test_1gprs';
    var message = "1234";
    var username = 'test';
    var passwd = 'test';
    var count = 1;

    var args = process.argv.splice(2), key, value;
   for(var i=0; i < args.length; i++) {
        key = args[i], value = args[++i];
        if('-help' == key) {
            console.log('help: node client.js -d m_test_dev -h http://iot-i.cn;6690 -u test -p test -m "hello world"');
            process.exit(1);
        } else if('-d' == key) {
            device_id = value;
        } else if('-h' == key) {
            api_url = value;
        } else if('-u' == key) {
            username = value;
        } else if('-p' == key) {
            passwd = value;
        } else if('-m' == key) {
            message = value;
        } else if('-t' == key) {
            count = parseInt(value);
        } else {
            console.log("Unknow options with ", key);
            process.exit(1)
        }
    }

    var client = new APP(api_url);
    
    client.auth('test', 'test')
    .then(function() {
        return client.add_monitor('', '', device_id)
    })
    .then(function() {
        return client.monitor_auth()
    })
    .then(function() {
        return client.push_message(device_id, message, count)
    })
    .then(function() {
        return client.get_frame(10000, count)
    })
    .then(function(frame) {
        console.log("get frame: ", frame)
    })
    .then(function() {
        client.end(device_id)
    })
    .catch(function(err) {
        console.log("Error with : ", err);
        client.end(device_id)
    })
}

main();
